{# Overseer > Deployments' pieces (overseer_deploy_actions): one recipe for a control that asks ``aegis deploy-agent`` (a write, answered by its job, which swaps into the card's job panel); the destroy form that the server and a failed provision both offer, and that provision's resume (``stopped``); a step on the way to a server; why the agent cannot be asked; the job panel; where something runs (``place``: a provider's mark and name); a fragment's reload while it waits on the agent; a part of the new-server form (``panel``: waiting on the agent, why it failed, or what it found); a picker of providers by their logos, and a terminal way folded under it (``picker``, ``by_hand``); and ``note``, the muted paragraph's classes. #} {% from "components/macros/form.html" import action, field, text_input %} {% from "components/macros/icons.html" import icon %} {% from "components/macros/layout.html" import avatar, badge, card, command, dropdown, menu_item, progress %} {% macro note() %}text-sm text-aegis-muted space-y-2{% endmacro %} {# What most can leave folded: ``label`` the line that opens it, the caller's body under it, ``attrs`` on its ``
``. #} {% macro fold(label, attrs="") %}
{{ label }}
{{ caller() }}
{% endmacro %} {# ``reload`` (overseer_deploy_actions: url, trigger) as the hx attributes a fragment asks again with, or nothing once it is ready. #} {# A link to another part of Overseer, swapped into its main area. #} {% macro goto(url, label, attrs="") %}{{ label }}{% endmacro %} {% macro reloads(reload) %}{% if reload %} hx-get="{{ reload.url }}" hx-trigger="{{ reload.trigger }}"{% endif %}{% endmacro %} {# What a part of the new-server form waits on the agent for, over a moving bar. #} {% macro waiting(said) %}

{{ said }}

{{ progress(none, label=said) }}
{% endmacro %} {# Why a part of the new-server form could not be had (``f.error``, after ``said``), and asking for it again (``f.retry``) in its place. #} {% macro failed(said, f) %}

{{ said }}{{ f.error }}

{{ action("Try again", 'data-retry hx-get="' ~ f.retry ~ '" hx-target="closest [data-part]" hx-swap="outerHTML"') }} {% endmacro %} {# A part the agent is asked for (``f``, from overseer_deploy_form: the new-server form's PANELS, the card's rollback and live build): waiting on the agent, why it could not be had, or what it found (the caller's body). ``name`` is its data attribute; ``boxed`` frames it. #} {% macro panel(name, f, said_waiting, said_failed, boxed=True) %}
{% if f.state == "loading" %} {{ waiting(said_waiting) }} {% elif f.state == "failed" %} {{ failed(said_failed, f) }} {% else %} {{ caller() }} {# When the agent looked (kept until a refresh, or a command changes it), and asking it again. #} {% if f.refresh %}

{% if f.checked %}Checked {{ f.checked }}{% endif %}

{% endif %} {% endif %}
{% endmacro %} {% macro place(where) %}
{{ avatar(where.name, where.logo, "lg") }} {{ where.name }} {% if where.console %}Open console{% endif %}
{% endmacro %} {% macro asks(url) %}data-write hx-post="{{ url }}" hx-target="#deploy-job" hx-swap="innerHTML"{% endmacro %} {% macro ask(label, partials, verb, tone="default") %} {% set attrs %}{{ asks(partials ~ "/" ~ verb) }} data-ask="{{ verb }}"{% endset %} {{ action(label, attrs, tone) }} {% endmacro %} {% macro destroy_form(partials, name) %}
{% call field("Type " ~ name ~ " to delete the server, its firewall, DNS record and SSH key") %}{{ text_input("confirm", required=True, pattern=name|exactly) }}{% endcall %} {{ action("Destroy", "", "danger", type="submit") }}
{% endmacro %} {# A provision that stopped part way (``name``, the server it made, still billed): resume it, or, folded away, delete what it made. #} {% macro stopped(partials, name) %}

What it created is still in your account, and billed. Resuming carries on from where it stopped.

{{ ask("Resume", partials, "resume", tone="primary") }} {% call fold("Delete what it made instead", "data-delete") %}{{ destroy_form(partials, name) }}{% endcall %}
{% endmacro %} {# A step (``s``: key, title, status, summary, open), numbered ``n``: its answer in a line once done, its body (the caller) while ``open`` (at hand, or optional), and only its name while still to come. #} {% macro step(n, s) %} {% set tone = {"done": "border-aegis-teal text-aegis-teal", "current": "border-aegis-teal text-aegis-text", "optional": "border-dashed border-aegis-border text-aegis-text"}.get(s.status, "border-aegis-border text-aegis-muted") %}
  • {{ s.title }}{% if s.summary %} {{ s.summary }}{% endif %}

    {% if s.open %}
    {{ caller() }}
    {% endif %}
  • {% endmacro %} {# Why the deploy agent cannot be asked (``agent``, its state), or, when it can, the checkout it runs in (each worktree or copy runs its own). #} {% macro agent_note(agent) %} {% if agent.state == "ready" %}

    Deploy agent: {{ agent.said }}

    {% elif agent.state == "no-redis" %}

    Deploying from here needs Redis: the Overseer hands each action to aegis deploy-agent on your machine through the project's Redis, so your SSH key never enters the app.

    {{ command("aegis add redis") }}

    {% elif agent.state == "unreachable" %}

    Redis did not answer: {{ agent.error }}

    {% elif agent.state == "stale" %}

    {{ agent.error }} {{ agent.said }}

    {% elif agent.state == "no-agent" %}

    No deploy agent is running: make serve runs it as the stack's deploy-agent service, whose log (docker compose logs deploy-agent) says why it stopped, or run {{ command(agent.command) }} in the project directory.

    What is the deploy agent?

    A small process on your machine that does what you ask on this page: create a server, deploy, back up, roll back, destroy. make serve runs it as the stack's deploy-agent service, on the aegis this project pins.

    It runs there, not in the app, and signs through your machine's ssh-agent, so your SSH key never leaves it. The cloud token comes from Deployments > Settings (or .env), or from the agent's own environment.

    {% endif %} {% endmacro %} {# A provider as the picker shows it: its logo and name. #} {% macro mark(d) %}{{ avatar(d.name, d.logo) }}{{ d.name }}{% endmacro %} {# A picker of providers, each by its logo and name (the Domain and Server steps): ``model`` is the Alpine field it sets, ``chosen`` the one it opens on (marked until another is picked), ``nothing`` the label of picking none, if allowed. #} {% macro picker(label, model, providers, chosen, nothing=None) %} {% set items = ([{"key": "", "name": nothing}] if nothing else []) + providers %} {% set current %}{% for d in items %}{{ mark(d) if d.key else d.name }}{% endfor %}{% endset %}

    {{ label }}

    {% call dropdown(current, align="left") %} {% for d in items %} {{ menu_item(mark(d) if d.key else d.name, 'data-value="' ~ d.key ~ '" x-on:click="' ~ model ~ ' = \'' ~ d.key ~ '\'" x-bind:aria-current="' ~ model ~ ' === \'' ~ d.key ~ '\' ? \'true\' : null"' ~ (' aria-current="true"' if d.key == chosen else '')) }} {% endfor %} {% endcall %}
    {% endmacro %} {# A terminal way (``way``; ``entries``: name, command, what it reads from the environment), folded in a note until opened. Under a picker (``model``), an entry with a ``key`` shows only while it is picked, and the note only while something is. #} {% macro by_hand(entries, way, model=None, title="Prefer the terminal?") %}
    {{ title }}
    {% endmacro %} {# A deployed server's card: deploys come from the dev Overseer, or CI. #} {% macro deployed_card(actions) %} {% call card("Deploys", id="card-deploy") %}

    This is a deployed server. Redeploys come from the dev Overseer, or from CI:

    {{ command(actions.command) }}

    {% endcall %} {% endmacro %} {# A deploy job (_job.html), streamed from ``actions.events`` while the page is open; one that has ended, without it, streams nothing. #} {% macro job_panel(actions) %}
    {% with job=actions.job %}{% include "pages/overseer/deployments/_job.html" %}{% endwith %}
    {% endmacro %}